package com.example.victory.filter;

import com.example.victory.utils.JsonHelper;
import com.example.victory.common.web.WebHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 打印请求日志信息，默认不开启
 */
@Component
@WebFilter(urlPatterns = {"/*"}, filterName = "sessionFilter")
public class SpringBootFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(SpringBootFilter.class);

    public static final ThreadLocal<Boolean> DebugThreadLocal = new ThreadLocal<>();
    /**
     * 调试日志打印开关
     */
    @Value("${app.debug:false}")
    private Boolean debug;

    /**
     * 请求慢速毫秒数，默认2秒
     */
    @Value("${app.web.slowMs:2000}")
    private Integer webSlowMs;

    /**
     * 特殊接口打印日志开关
     */
    @Value("${app.debug.apis:}")
    private String debugApis;
    private String lastDebugApis;
    private Map<String, Boolean> debugApiMap = new ConcurrentHashMap<>();

    /**
     * 重新加载特殊接口打印日志开关
     */
    private void initDebugApis() {
        debugApiMap.clear();
        if (!StringUtils.isEmpty(debugApis)) {
            String[] apis = debugApis.split(",");
            if (apis != null && apis.length > 0) {
                for (int i = 0; i < apis.length; ++i) {
                    debugApiMap.put(apis[i].trim(), true);
                }
            }
        }
        lastDebugApis = debugApis;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        initDebugApis();
    }

    /**
     * 所有http请求进入过滤器
     *
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        DebugThreadLocal.set(debug);
        long start = System.currentTimeMillis();

        if (request instanceof HttpServletRequest) {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = (HttpServletResponse) response;

            // 排除掉swagger自身接口
            String servletPath = httpRequest.getServletPath();
            if (servletPath != null && (servletPath.startsWith("/swagger-resources") ||
                    servletPath.equalsIgnoreCase("/swagger-ui.html") ||
                    servletPath.equalsIgnoreCase("/api-docs") ||
                    servletPath.contains("springfox"))) {
                chain.doFilter(request, response);
                return;
            }

            // 获取请求id
            String requestId = WebHelper.getRequestId(httpRequest, httpResponse);
            if (StringUtils.isEmpty(requestId)) {
                requestId = this.createRequestId(httpRequest, httpResponse);
            }

            // 如果没有会话id，创建新会话；
            String sessionid = WebHelper.getSessionId(httpRequest, httpResponse);
            if (StringUtils.isEmpty(sessionid)) {
                sessionid = this.createSession(httpRequest, httpResponse);
            }

            MyHttpServletRequestWrapper requestWrapper = null;
            // 判断是否打印调试日志
            boolean isDebug = (debug != null && debug.booleanValue());
            if (!isDebug) {
                if (!Objects.equals(lastDebugApis, debugApis)) {
                    initDebugApis();
                }
                isDebug = debugApiMap.get(httpRequest.getRequestURI()) != null;
            }
            DebugThreadLocal.set(isDebug);

            // 打印请求开始日志
            if (isDebug) {
                String contentType = httpRequest.getHeader("content-type");
                if (!StringUtils.isEmpty(contentType) && !contentType.startsWith("multipart/form-data")) {
                    requestWrapper = new MyHttpServletRequestWrapper(httpRequest);
                }
                logBegin(httpRequest, httpResponse, requestWrapper);
            }

            // 进入系统默认过滤流程
            chain.doFilter(requestWrapper != null ? requestWrapper : httpRequest, response);

            // 打印请求结束日志，再其他地方打印
            if (isDebug) {
                long cost = System.currentTimeMillis() - start;
                logger.info("请求耗时: {}ms，请求会话: {}，请求id: {}", cost, sessionid, requestId);
            }
        } else {
            chain.doFilter(request, response);
        }

        long cost = System.currentTimeMillis() - start;
        if (webSlowMs != null && cost > webSlowMs) {
            String url = "";
            if (request instanceof HttpServletRequest) {
                url = ((HttpServletRequest) request).getRequestURL().toString();
            }
            logger.warn("slow web request, cost:{}ms, url:{}", cost, url);
        }
    }

    /**
     * 请求开始日志
     *
     * @param httpRequest
     * @param httpResponse
     * @param requestWrapper
     */
    private void logBegin(HttpServletRequest httpRequest, HttpServletResponse httpResponse, MyHttpServletRequestWrapper requestWrapper) {
        // 打印用户信息
        String sessionid = WebHelper.getSessionId(httpRequest, httpResponse);
        String requestId = WebHelper.getRequestId(httpRequest, httpResponse);

        StringBuilder sb = new StringBuilder();
        // 打印请求信息
        String info = MessageFormat.format("请求开始:\n请求信息:\n\t网址: {0}\n\t方法: {1}\n\t参数: {2}\n\t远程主机: {3}\n\t会话id: {4}\n\t请求id: {5}", httpRequest.getRequestURL(), httpRequest.getMethod(), httpRequest.getQueryString(), httpRequest.getRemoteHost(), sessionid, requestId);
        sb.append(info);

        // 打印header
        sb.append("\n请求头部:\n");
        Enumeration<String> headerNames = httpRequest.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String value = httpRequest.getHeader(headerName);
            sb.append("\t").append(headerName).append(": ").append(value).append("\n");
        }

        // 打印body
        if (requestWrapper != null) {
            String body = this.getBody(requestWrapper);
            String prettyJson = null;
            if (!StringUtils.isEmpty(body)) {
                try {
                    prettyJson = JsonHelper.toPrettyJson(body);
                    sb.append("请求内容:\n").append(prettyJson);
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                    sb.append("请求内容:\n").append(body);
                }
            }
        }

        logger.info(sb.toString());
    }

    /**
     * 创建会话
     *
     * @return
     */
    private String createSession(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
        String sessionid = UUID.randomUUID().toString();
        httpResponse.addHeader(WebHelper.SESSIONID_HEADER, sessionid);
        return sessionid;
    }

    /**
     * 创建请求id
     *
     * @return
     */
    private String createRequestId(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
        String id = UUID.randomUUID().toString();
        httpResponse.addHeader(WebHelper.REQEUST_ID_HEADER, id);
        return id;
    }

    private String getBody(HttpServletRequest httpRequest) {
        StringBuilder sb = new StringBuilder();
        BufferedReader br = null;
        try {
            br = httpRequest.getReader();
            String str;
            while ((str = br.readLine()) != null) {
                sb.append(str);
            }
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return sb.toString();
    }

    @Override
    public void destroy() {

    }
}
